package com.wangl.spring.suggestion;

import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.apache.commons.collections.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import static com.wangl.spring.utils.PsiCustomUtil.getNullableContainingFile;

/**
 * @ClassName SuggestionNodeTree
 * @Description TODO
 * @Author wangl
 * @Date 2023/3/30 14:27
 */
public class SuggestionNodeTree {

    private SuggestionKeyNode virtualRoot = SuggestionKeyNode.newInstance("", null);
    private Map<String,Set<PsiElement>> configurationPropertiesMap = new ConcurrentHashMap<>();
    private Map<String,Set<PsiElement>> valueMap = new ConcurrentHashMap<>();
    private Map<String,Set<PsiElement>> conditionalOnPropertyMap = new ConcurrentHashMap<>();
    public List<SuggestionKeyNode> getRootNodes(){
        return virtualRoot.getChildren();
    }

    public void insertNode(String prefix, PsiElement psiElement, SuggestionNodeType type){
        PsiFile psiFile = getNullableContainingFile(psiElement);
        if (psiFile == null || !psiFile.isValid()){
            return;
        }
        switch (type){
            case ANNOTATION_CONFIGURATION_PROPERTIES:
                configurationPropertiesMap.computeIfAbsent(prefix, k -> new CopyOnWriteArraySet<>()).add(psiElement);
                break;
            case ANNOTATION_CONDITIONAL_ON_PROPERTY:
                conditionalOnPropertyMap.computeIfAbsent(prefix, k -> new CopyOnWriteArraySet<>()).add(psiElement);
                break;
            case ANNOTATION_VALUE:
                valueMap.computeIfAbsent(prefix, k -> new CopyOnWriteArraySet<>()).add(psiElement);
                break;
            default:
                break;
        }
    }

    private void insertTreeNode(String prefix, SuggestionNodeType type){
        String[] nodes = prefix.split("\\.");
        SuggestionKeyNode suggestionKeyNode = recursionInsertNode(virtualRoot, nodes, 0);
        suggestionKeyNode.setType(type);
    }

    private SuggestionKeyNode recursionInsertNode(SuggestionKeyNode parent, String[] prefix, int level){
        if (level == prefix.length){
            return parent;
        }
        Optional<SuggestionKeyNode> keyNodeOptional = parent.getChildren().stream()
                .filter(node -> node.getText().equals(prefix[level]))
                .findFirst();
        if (keyNodeOptional.isPresent()){
            SuggestionKeyNode currentNode = keyNodeOptional.get();
            return recursionInsertNode(currentNode, prefix, level + 1);
        }else {
            SuggestionKeyNode currentCreatedNode = SuggestionKeyNode.newInstance(prefix[level], parent);
            parent.addChild(currentCreatedNode);
            currentCreatedNode.setLayerLevel(level + 1);
            return recursionInsertNode(currentCreatedNode, prefix, level + 1);
        }
    }

    public Set<PsiElement> searchTargets(String prefix){
        Set<PsiElement> resultSet = new HashSet<>();
        resultSet.addAll(configurationPropertiesMap.getOrDefault(prefix, Collections.emptySet()));
        resultSet.addAll(valueMap.getOrDefault(prefix, Collections.emptySet()));
        resultSet.addAll(conditionalOnPropertyMap.getOrDefault(prefix, Collections.emptySet()));
        return resultSet;
    }

    public SuggestionKeyNode searchNode(String prefix){
        String[] nodes = prefix.split("\\.");
        return recursionQueryNode(virtualRoot, nodes, 0);
    }

    private SuggestionKeyNode recursionQueryNode(SuggestionKeyNode parent, String[] prefix, int level){
        if (level == prefix.length){
            return parent;
        }
        Optional<SuggestionKeyNode> keyNodeOptional = parent.getChildren().stream()
                .filter(node -> node.getText().equals(prefix[level]))
                .findFirst();
        if (keyNodeOptional.isPresent()){
            SuggestionKeyNode currentNode = keyNodeOptional.get();
            return recursionQueryNode(currentNode, prefix, level + 1);
        }
        return null;
    }

    public void clear(){
        virtualRoot.getChildren().clear();
        configurationPropertiesMap.clear();
        valueMap.clear();
        conditionalOnPropertyMap.clear();
    }

    public Set<PsiElement> getConfigurationPropertiesElement(String prefix) {
        return configurationPropertiesMap.get(prefix);
    }

    public void remove(PsiFile psiFile) {
        removeConfigurationProperties(psiFile);
        removeValue(psiFile);
        removeConditionalOnProperty(psiFile);
    }

    private void removeConfigurationProperties(PsiFile psiFile){
        Iterator<Map.Entry<String, Set<PsiElement>>> iterator = configurationPropertiesMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Set<PsiElement>> entry = iterator.next();
            List<PsiElement> needDelete = new ArrayList<>();
            entry.getValue().forEach(psiElement -> {
                PsiFile file = getNullableContainingFile(psiElement);
                if (file == null || psiFile.equals(file)){
                    needDelete.add(psiElement);
                }
            });
            entry.getValue().removeAll(needDelete);
            if (entry.getValue().isEmpty()){
                iterator.remove();
            }
        }
    }

    private void removeValue(PsiFile psiFile){
        Iterator<Map.Entry<String, Set<PsiElement>>> iterator = valueMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Set<PsiElement>> entry = iterator.next();
            List<PsiElement> needDelete = new ArrayList<>();
            entry.getValue().forEach(psiElement -> {
                PsiFile file = getNullableContainingFile(psiElement);
                if (file == null || psiFile.equals(file)){
                    needDelete.add(psiElement);
                }
            });
            entry.getValue().removeAll(needDelete);
            if (entry.getValue().isEmpty()){
                iterator.remove();
            }
        }
    }

    private void removeConditionalOnProperty(PsiFile psiFile){
        Iterator<Map.Entry<String, Set<PsiElement>>> iterator = conditionalOnPropertyMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Set<PsiElement>> entry = iterator.next();
            List<PsiElement> needDelete = new ArrayList<>();
            entry.getValue().forEach(psiElement -> {
                PsiFile file = getNullableContainingFile(psiElement);
                if (file == null || psiFile.equals(file)){
                    needDelete.add(psiElement);
                }
            });
            entry.getValue().removeAll(needDelete);
            if (entry.getValue().isEmpty()){
                iterator.remove();
            }
        }
    }

    public synchronized void rebuildTree() {
        virtualRoot.getChildren().clear();
        build();
    }

    private void build(){
        configurationPropertiesMap.forEach((k, v) -> insertTreeNode(k,SuggestionNodeType.ANNOTATION_CONFIGURATION_PROPERTIES));
        valueMap.forEach((k,v) -> insertTreeNode(k, SuggestionNodeType.ANNOTATION_VALUE));
        conditionalOnPropertyMap.forEach((k,v) -> insertTreeNode(k, SuggestionNodeType.ANNOTATION_CONDITIONAL_ON_PROPERTY));
    }
}
